Skip to content

AzLocal.UpdateManagement v0.8.7 - on-prem solution-update sideloading automation (5 new cmdlets, Step.6 pipeline, de-numbered filenames)#76

Merged
NeilBird merged 19 commits into
mainfrom
users/nebird/development
Jun 11, 2026
Merged

AzLocal.UpdateManagement v0.8.7 - on-prem solution-update sideloading automation (5 new cmdlets, Step.6 pipeline, de-numbered filenames)#76
NeilBird merged 19 commits into
mainfrom
users/nebird/development

Conversation

@NeilBird

Copy link
Copy Markdown
Owner

AzLocal.UpdateManagement v0.8.7 - On-prem solution-update sideloading automation

Opt-in, off-by-default automation for Azure Local clusters that cannot pull solution updates from Azure directly. A new self-hosted Step.6 pipeline robocopies update media to each cluster's import share, verifies the SHA256 over WinRM, runs Add-SolutionUpdate, and flips the UpdateSideloaded=True cluster tag so the downstream apply (now Step.7) picks it up. The multi-hour copy runs in a detached Windows Scheduled Task and the pipeline is driven as a re-entrant state machine on a frequent CRON, so no individual run is long-lived.

Module export count grows 55 -> 60.

Summary

  • 78 files changed, ~5,127 insertions / ~541 deletions, 24 new files.
  • Full local Pester suite green: 1084 passed, 0 failed, 1 skipped.
  • Module manifest + .psm1 version pinned to 0.8.7; all bundled pipeline templates pin GENERATED_AGAINST_MODULE_VERSION = '0.8.7'.

New Public cmdlets (5)

Cmdlet Purpose
Update-AzLocalSideloadCatalog Validates / refreshes the sideload catalog (Solution + OEM SBE packageType entries) used to resolve which media to stage.
Resolve-AzLocalSideloadPlan Resolves the per-cluster sideload plan (target media, import share, current state) from inventory + catalog.
Invoke-AzLocalSideloadUpdate Re-entrant state-machine driver (Planned -> Copying -> Copied -> Verified -> Imported -> SideloadFlagged): spawns / re-attaches the detached robocopy Scheduled Task, verifies SHA256 over WinRM, runs Add-SolutionUpdate, sets UpdateSideloaded=True.
Export-AzLocalSideloadStatusReport Emits the per-cluster sideload status CSV + JUnit XML + step outputs.
Add-AzLocalSideloadStepSummary Renders the Step.6 markdown step summary (per-state table + shared-state contract + exit conditions).

New Step.6 sideload pipeline

  • sideload-updates.yml (GitHub Actions + Azure DevOps), opt-in via the SIDELOAD_UPDATES repository variable.
  • Targets a self-hosted runner (runs-on: [self-hosted, azlocal-sideload]) / agent pool (pool: { name, demands: azlocal-sideload }).
  • Manual dispatch + */30 CRON poll advances the state machine between runs.
  • Catalog (Solution + OEM SBE), shared-UNC state for multi-runner coordination, a Key Vault-sourced AD credential for cluster WinRM, and robocopy throttling are documented in Automation-Pipeline-Examples/docs/sideload.md and sideload-robocopy.md.

BREAKING changes

  1. Pipeline filenames de-numbered. The Step.N_ filename prefix is removed (e.g. Step.7_apply-updates.yml -> apply-updates.yml). The in-pipeline Step.N - display names are unchanged, so operator-facing ordering is preserved. Update-AzLocalPipelineExample is now rename-aware: it matches each destination pipeline by a stable logical id (embedded # AZLOCAL-PIPELINE-ID: marker, with legacy-filename aliases) and AUTO-RENAMES any older Step.N_*.yml on disk to the new name while preserving the consumer's BEGIN/END-AZLOCAL-CUSTOMIZE CRON edits (emits a RenamedFrom result field + a required-check warning).
  2. Display-step renumber to make room for sideload at Step.6: apply-updates 6 -> 7, monitor-updates 7 -> 8, fleet-update-status 8 -> 9, fleet-health-status 9 -> 10.

Both breaking changes only affect consumers that pin on the old Step.N_*.yml filenames; Update-AzLocalPipelineExample migrates them automatically.

Sideload-aware existing steps (byte-identical output when sideload is off)

  • Step.1 inventory can emit the UpdateAuthAccountId column (-IncludeSideloadColumns, auto-enabled when SIDELOAD_UPDATES is set).
  • Step.2 tag management can set the UpdateAuthAccountId tag from CSV.
  • Step.3 schedule advisor can emit a recommended sideload CRON (apply window minus SIDELOAD_LEAD_DAYS).

Bug fix - in-flight monitor "Current Step"

Format-AzLocalUpdateRun (and the duplicate logic in Get-AzLocalFleetStatusData) selected CurrentStep from the top-level properties.progress.steps array only. Azure Local nests a coarse wrapper step that stays InProgress for the entire run, so the Step.8 monitor and fleet-status report always rendered "Start update" instead of the real current activity. Both paths now reuse Get-DeepestActiveStep to walk to the deepest InProgress/Error/Failed leaf (preserving the (FAILED) suffix), matching CurrentStepDetail / Get-CurrentStepPath and the standard update-progress output. Adds two regression tests.

Tests

  • 18 new mock-based Pester tests covering the 5 new sideload cmdlets.
  • 2 new Format-AzLocalUpdateRun regression tests for the deepest-step CurrentStep fix.
  • Export-count assertion bumped 55 -> 60.
  • Full suite: 1084 passed, 0 failed, 1 skipped.

Documentation

  • CHANGELOG.md - full v0.8.7 entry incl. the "Current Step" fix.
  • README.md - "What's New in v0.8.7" section + all 5 new cmdlets referenced.
  • Automation-Pipeline-Examples/README.md and docs/appendix-pipelines.md - sideload pipeline coverage.
  • Automation-Pipeline-Examples/docs/sideload.md + sideload-robocopy.md - new sideload operator docs.

PSGallery readiness

  • Module manifest ModuleVersion = '0.8.7' (matches .psm1 $script:ModuleVersion).
  • Version pins consistent across all 22 bundled templates (no stale 0.8.6 / 0.8.5 references).
  • Publishing to PSGallery is performed via Publish-Module.ps1 after this PR merges to main (not run as part of this PR).

NeilBird added 19 commits June 11, 2026 09:53
…behaviour-preserving) + reuse in Start-AzLocalClusterUpdate
…e helper (Solution download/verify, SBE staged-folder, atomic concurrent-safe cache)
…on, import-share target resolver, detached robocopy worker script + scheduled-task register/cleanup
…remote SHA256 verify, remote solution import (Expand-Archive/Add-SolutionUpdate/Get-SolutionUpdate poll + SBE AdditionalContentRequired)
…, fleet-update->9, fleet-health->10)

Vacates Step.6 for the upcoming on-prem sideload pipeline (Group G). Renames the 8 GH/ADO YAMLs via git mv and updates all current-state references across .ps1 source, Pester tests, the schedule example YAML, the module README, and operator docs (Automation-Pipeline-Examples README + appendix-pipelines). Immutable historical changelog/release-history entries preserved; one-level 'formerly Step.N' annotations shifted; multi-level history chains appended (not mutated). Module imports clean (60 commands), no parse errors.
Step.1 (Get-/Invoke-AzLocalClusterInventory): opt-in -IncludeSideloadColumns switch (auto-resolved from SIDELOAD_UPDATES repo var) exports a numeric UpdateAuthAccountId column before ResourceId in cluster-inventory CSV/JSON. Step.2 (Set-AzLocalClusterUpdateRingTag): new -UpdateAuthAccountIdValue param (ByResourceId) + UpdateAuthAccountId CSV column handling, validated ^\d{1,3}$ (invalid -> per-cluster Failed), written via existing merge-tags PATCH. Byte-identical output when SIDELOAD_UPDATES is unset/false (column omitted, tag not required). Module imports clean (60 commands).
…3 sideload advisor

- New Automation-Pipeline-Examples/{github-actions,azure-devops}/Step.6_sideload-updates.yml:
  opt-in, self-hosted runner/agent (label/demand azlocal-sideload), re-entrant
  state-machine, no default schedule, master gate SIDELOAD_UPDATES=='true',
  KV auth via OIDC (GH) / federated SP (ADO).
- Export-AzLocalApplyUpdatesScheduleAudit: new -SideloadEnabled / -SideloadLeadDays
  params and a 'Recommended sideload schedule (Step.6 - opt-in)' section that
  derives a per-apply-window kickoff cron (apply firing shifted back LeadDays)
  plus the */30 poll cron. Gated on param override -> SIDELOAD_UPDATES env.
- FIX: local accumulator renamed to \; PowerShell vars are
  case-insensitive so \ aliased the [bool]\ param
  and clobbered it to \False (explicit -SideloadEnabled \True rendered nothing).
- Step.3 apply-updates-schedule-audit.yml (GH+ADO): wire SIDELOAD_UPDATES /
  SIDELOAD_LEAD_DAYS env/vars through to the audit step.
…name-aware Update

Breaking change (no consumers yet): drop the Step.N_ prefix from all bundled
pipeline example filenames so renames/renumbering no longer strand customer
CRONs. Pipelines are now matched to their destination by a stable logical ID.

- Add Private/Get-AzLocalPipelineManifest.ps1: 11-row manifest mapping each
  logical pipeline Id -> canonical FileName, DisplayStep, IntroducedIn, and
  legacy Step.N_*.yml Aliases (post-R and pre-R names for the 4 renumbered ones).
- Add Private/Get-AzLocalPipelineId.ps1: parse the AZLOCAL-PIPELINE-ID header
  comment from a YAML body.
- Register both privates in the .psd1 NestedModules.
- Rename 22 example YAMLs (GH + ADO) dropping Step.N_ prefix; prepend the
  AZLOCAL-PIPELINE-ID header comment as line 1. Display names/artifacts inside
  the YAML keep Step.N labels (intentional).
- Update-AzLocalPipelineExample: match destination files by logical ID
  (canonical name -> embedded ID -> legacy alias filename), rename matched
  files to the canonical name while preserving BEGIN/END-AZLOCAL-CUSTOMIZE
  bodies (e.g. schedule CRONs), and emit a new RenamedFrom output field plus
  rename/platform-implication warnings. Updated .DESCRIPTION + OUTPUTS docs.
- Read-AzLocalApplyUpdatesYamlCrons: switch directory discovery to ID-based
  matching (ID == apply-updates), with a name fallback that explicitly
  EXCLUDES the de-numbered apply-updates-schedule-audit.yml sibling so its
  poll crons are never mistaken for apply-updates windows.
- Update advisor + doc-comment filename references to the de-numbered names.
…s in current-state docs

De-numbered Step.N_ filename references in current-state docs (Automation README + appendix-pipelines, module README body, cmdlet-reference, rbac) to match the Group P file renames. Left Step.N display labels, #step-N anchors, and all historical records (CHANGELOG, release-history, README Release History appendix, ITSM planning docs) untouched. Also removed 4 stray untracked numbered duplicate YAMLs left by an earlier Update/test run.
…appendix entry

Added the missing Step.6 sideload row to the section-1 pipeline table, a full Step 6 reference section in appendix-pipelines.md, and two new docs (sideload.md architecture/runbook + sideload-robocopy.md throttling). Updated pipeline count nine/ten -> eleven and fixed the section 1.1 naming explanation to reflect de-numbered filenames.
psd1/psm1 ModuleVersion 0.8.6->0.8.7; ReleaseNotes prepend v0.8.7 (4959 chars); 55->60 export count; CHANGELOG + README What's New + release-history pointer; GENERATED_AGAINST_MODULE_VERSION 0.8.6->0.8.7 across all 22 bundled pipeline templates; Tests version + function-count (55->60) assertions.
…unc assertion

Updated all test filename literals from Step.N_*.yml to de-numbered names (glob filters, ForEach paths, banner/Node24/smoke/calendar/copy/update tests); added the 5 v0.8.7 sideload cmdlets to the expected-functions list; bumped both function-count assertions 55->60; de-numbered the Ring CRON calendar column header to '(apply-updates pipeline)' in the cmdlet + audit doc + 3 tests; added the Node24 opt-in to sideload-updates.yml (GH). Full suite: 1070 passed, 0 failed, 1 skipped.
…load cmdlets

Adds 18 behaviour tests covering the 5 new on-prem sideloading public cmdlets: Update-AzLocalSideloadCatalog (offline HTML parse + SBE-preserving merge + WhatIf no-write), Resolve-AzLocalSideloadPlan (UnknownAuthAccountId, due/Planned with catalog, NoCatalogEntry), Invoke-AzLocalSideloadUpdate state machine (Skip/Start/Imported/InProgress/stale-Failed/Copied-import/Error), Export-AzLocalSideloadStatusReport (markdown+JUnit, Failed flagging, plan-error rows, OutputPath file writes), and Add-AzLocalSideloadStepSummary (delegates to report + step summary). Full suite: 1088 passed, 0 failed, 1 skipped.
…t active step, not 'Start update' wrapper

Format-AzLocalUpdateRun (and the duplicate logic in Get-AzLocalFleetStatusData) selected CurrentStep from the TOP-LEVEL progress.steps array only. Azure Local nests a coarse wrapper step ('Start update') that stays InProgress for the entire run, so CurrentStep always rendered 'Start update' in the in-flight monitor. Now both paths reuse Get-DeepestActiveStep to walk to the deepest InProgress/Error/Failed leaf (preserving the (FAILED) suffix), matching CurrentStepDetail / Get-CurrentStepPath and the standard update-progress output. Adds 2 Format-AzLocalUpdateRun regression tests.
@NeilBird
NeilBird merged commit 32ea392 into main Jun 11, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant